// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
#include "pch.h"
#include "Common\DirectXHelper.h"
#include "LiveTileScheduler.h"
#include "ImageTile.h"
#include <robuffer.h>
#include <wrl.h>

using namespace Weathr;

using namespace concurrency;
using namespace Microsoft::WRL;
using namespace Platform;
using namespace std;
using namespace Windows::Graphics::Imaging;
using namespace Windows::Foundation;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;
using namespace Windows::UI::Notifications;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Media::Imaging;

const uint32_t MinFileIndex = 0u;
const uint32_t MaxFileIndex = 4u;
String^ TilesFolder = "Tiles";
String^ SecondaryHugeFilename = "secondaryHuge.png";
String^ SecondaryWideFilename = "secondaryWide.png";
String^ SecondarySquareFilename = "secondarySquare.png";

// Retrieves the raw pixel data from the provided IBuffer object.
// Warning: The lifetime of the returned buffer is controlled by
// the lifetime of the buffer object that's passed to this method.
// When the buffer has been released, the pointer becomes invalid
// and must not be used.
byte* GetPointerToPixelData(IBuffer^ pixelBuffer, unsigned int* length)
{
    if (length != nullptr)
    {
        *length = pixelBuffer->Length;
    }
    // Query the IBufferByteAccess interface.
    ComPtr<IBufferByteAccess> bufferByteAccess;
    reinterpret_cast<IInspectable*>(pixelBuffer)->QueryInterface(IID_PPV_ARGS(&bufferByteAccess));

    // Retrieve the buffer data.
    byte* pixels = nullptr;
    bufferByteAccess->Buffer(&pixels);
    return pixels;
}


inline String^ GetHugeFileName(uint32_t index)
{
    return index + "h.png";
}

inline String^ GetWideFileName(uint32_t index)
{
    return index + "w.png";
}

inline String^ GetSquareFileName(uint32_t index)
{
    return index + "s.png";
}

LiveTileScheduler::LiveTileScheduler() : m_nextFileIndex(MinFileIndex), m_tileUpdater(TileUpdateManager::CreateTileUpdaterForApplication())
{
    m_tileUpdater->Clear();
    m_tileUpdater->EnableNotificationQueue(true);
}

task<void> LiveTileScheduler::UpdateLiveTileImagesAsync(Forecast^ forecast, UIElement^ hugeElement, UIElement^ wideElement, UIElement^ squareElement)
{
    auto index = GetNextFileIndex();
    vector<task<void>> tasks;
    tasks.emplace_back(InternalUpdateLiveTileImagesAsync(forecast, hugeElement, GetHugeFileName(index), 310, 310));
    tasks.emplace_back(InternalUpdateLiveTileImagesAsync(forecast, wideElement, GetWideFileName(index), 310, 150));
    tasks.emplace_back(InternalUpdateLiveTileImagesAsync(forecast, squareElement, GetSquareFileName(index), 150, 150));
    return when_all(begin(tasks), end(tasks)).then([this, index, tasks](task<void> previousTask)
    {
        try
        {
            previousTask.get();
            m_fileIndices.emplace_back(index);
        }
        catch (...)
        {
            // Ensure that all exceptions are at least observed.
            observe_all_exceptions<void>(begin(tasks), end(tasks));
            // Rethrow the one that was caught.
            throw;
        }
    });
}

void LiveTileScheduler::UpdateLiveTile()
{
    m_nextFileIndex = MinFileIndex;
    ImageTile wideTile;

    // Set tiles to expire after 1 day.
    auto calendar = ref new Windows::Globalization::Calendar();
    calendar->SetToNow();
    calendar->AddDays(1);
    auto expirationTime = calendar->GetDateTime();

    for (auto index : m_fileIndices)
    {
        wostringstream hugeImageSource;
        hugeImageSource << L"ms-appdata:///local/"
            << TilesFolder->Data()
            << L"/"
            << GetHugeFileName(index)->Data();

        wostringstream wideImageSource;
        wideImageSource << L"ms-appdata:///local/"
            << TilesFolder->Data()
            << L"/"
            << GetWideFileName(index)->Data();

        wostringstream squareImageSource;
        squareImageSource << L"ms-appdata:///local/"
            << TilesFolder->Data()
            << L"/"
            << GetSquareFileName(index)->Data();

        wideTile.SetImageFilePaths(hugeImageSource.str(), wideImageSource.str(), squareImageSource.str());

        // Create the notification and update the tile.
        auto notification = wideTile.GetTileNotification();
        notification->ExpirationTime = expirationTime;
        m_tileUpdater->Update(notification);
    }
}

task<void> LiveTileScheduler::UpdateSecondaryTileImagesAsync(Forecast^ forecast, UIElement^ hugeElement, UIElement^ wideElement, UIElement^ squareElement)
{
    vector<task<void>> tasks;
    tasks.emplace_back(InternalUpdateLiveTileImagesAsync(forecast, hugeElement, SecondaryHugeFilename, 310, 310));
    tasks.emplace_back(InternalUpdateLiveTileImagesAsync(forecast, wideElement, SecondaryWideFilename, 310, 150));
    tasks.emplace_back(InternalUpdateLiveTileImagesAsync(forecast, squareElement, SecondarySquareFilename, 150, 150));
    return when_all(begin(tasks), end(tasks)).then([this, tasks](task<void> previousTask)
    {
        try
        {
            previousTask.get();
        }
        catch (...)
        {
            // Ensure that all exceptions are at least observed.
            observe_all_exceptions<void>(begin(tasks), end(tasks));
            // Rethrow the one that was caught.
            throw;
        }
    });
}

Uri^ LiveTileScheduler::GetSecondaryTileHugeUri() const
{
    wostringstream ss;
    ss << L"ms-appdata:///local/"
        << TilesFolder->Data()
        << L"/"
        << SecondaryHugeFilename->Data();
    return ref new Uri(ref new String(ss.str().c_str()));
}

Uri^ LiveTileScheduler::GetSecondaryTileWideUri() const
{
    wostringstream ss;
    ss << L"ms-appdata:///local/"
        << TilesFolder->Data()
        << L"/"
        << SecondaryWideFilename->Data();
    return ref new Uri(ref new String(ss.str().c_str()));
}

Uri^ LiveTileScheduler::GetSecondaryTileSquareUri() const
{
    wostringstream ss;
    ss << L"ms-appdata:///local/"
        << TilesFolder->Data()
        << L"/"
        << SecondarySquareFilename->Data();
    return ref new Uri(ref new String(ss.str().c_str()));
}

uint32_t LiveTileScheduler::GetNextFileIndex()
{
    auto result = m_nextFileIndex;
    if (++m_nextFileIndex > MaxFileIndex)
    {
        m_nextFileIndex = MinFileIndex;
    }
    return result;
}

task<void> LiveTileScheduler::InternalUpdateLiveTileImagesAsync(Forecast^ forecast, UIElement^ element, String^ fileName, unsigned int imageWidth, unsigned int imageHeight)
{
    assert(IsMainThread());

    shared_ptr<BitmapEncoder^> encoder = make_shared<BitmapEncoder^>(nullptr);
    shared_ptr<IBuffer^> buffer = make_shared<IBuffer^>(nullptr);

    task<void> openFileTask = create_task(ApplicationData::Current->LocalFolder->CreateFolderAsync(TilesFolder, CreationCollisionOption::OpenIfExists)).then([fileName](StorageFolder^ folder)
    {
        assert(IsBackgroundThread());
        return folder->CreateFileAsync(fileName, CreationCollisionOption::ReplaceExisting);

    }, task_continuation_context::use_arbitrary()).then([](StorageFile^ file)
    {
        assert(IsBackgroundThread());
        return file->OpenAsync(FileAccessMode::ReadWrite);

    }, task_continuation_context::use_arbitrary()).then([](IRandomAccessStream^ stream)
    {
        assert(IsBackgroundThread());
        return BitmapEncoder::CreateAsync(BitmapEncoder::PngEncoderId, stream);

    }, task_continuation_context::use_arbitrary()).then([encoder](task<BitmapEncoder^> previousTask)
    {
        *encoder = previousTask.get();

    }, task_continuation_context::use_arbitrary());

    auto bitmap = ref new RenderTargetBitmap();
    task<void> renderTask = create_task(bitmap->RenderAsync(element)).then([bitmap]()
    {
        assert(IsMainThread());
        return bitmap->GetPixelsAsync();

    }).then([buffer, bitmap](task<IBuffer^> previousTask)
    {
        assert(IsMainThread());
        *buffer = previousTask.get();
    });

    // Capturing 'bitmap' here keeps the buffer alive throughout the operation.
    return (openFileTask && renderTask).then([bitmap, encoder, buffer, imageWidth, imageHeight]()
    {
        assert(IsMainThread());
        unsigned int length = 0;
        byte* pbytes = GetPointerToPixelData(*buffer, &length);
        auto pixels = ref new Array<unsigned char>(pbytes, length);
        auto currentDisplayInformation = Windows::Graphics::Display::DisplayInformation::GetForCurrentView(); // must be called from thread that is associated with a CoreWindow. 
        const double dpi = static_cast<double>(currentDisplayInformation->LogicalDpi);
        (*encoder)->SetPixelData(BitmapPixelFormat::Bgra8, BitmapAlphaMode::Premultiplied, imageWidth, imageHeight, dpi, dpi, pixels);
        return (*encoder)->FlushAsync();

    }, task_continuation_context::use_current());
}
